﻿using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;

namespace GeometryLib
{
    /// <summary>
    /// Helper class for JSON serialization and deserialization.
    /// By default, System.Text.Json.JsonSerializerOptions do not persist between calls to Serialize and Deserialize methods. This class provides a way to set global options for serialization and deserialization.
    /// </summary>
    public static class JsonHelper
    {
        private static readonly JsonSerializerOptions DefaultOptions;
        private static bool _isConfigured;
        private static JsonSerializerOptions GlobalJsonSerializerOptions { get; set; }
        private static readonly object LockObject = new();


        static JsonHelper()
        {
            var defaultOption = new JsonSerializerOptions
            {
                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                IncludeFields = true,
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
                UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
                WriteIndented = true,
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                //Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All),
                //Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic),
                PropertyNameCaseInsensitive = true
                
            };
            defaultOption.Converters.Add(new JsonStringEnumConverter());

            DefaultOptions = defaultOption;
            GlobalJsonSerializerOptions = DefaultOptions;
        }

        /// <summary>
        /// Set global JsonSerializerOptions for serialization and deserialization.
        /// </summary>
        /// <param name="optionsGetter">A function that takes the default JsonSerializerOptions and returns the modified JsonSerializerOptions.</param>
        /// <exception cref="InvalidOperationException">Thrown when global JsonSerializerOptions is already configured.</exception>
        public static void SetGlobalJsonSerializerOptions(Func<JsonSerializerOptions, JsonSerializerOptions> optionsGetter)
        {
            if (_isConfigured) throw new InvalidOperationException("Global JsonSerializerOptions is already configured.");
            lock (LockObject)
            {
                GlobalJsonSerializerOptions = optionsGetter(DefaultOptions);
                _isConfigured = true;
            }
        }

        private static JsonSerializerOptions GetDefaultOptions() => DefaultOptions;

        /// <summary>
        /// Get the global JsonSerializerOptions for serialization and deserialization.
        /// </summary>
        /// <returns>Current global JsonSerializerOptions.</returns>
        public static JsonSerializerOptions GetJsonSerializerOptions() => GlobalJsonSerializerOptions;

        /// <summary>
        /// Serialize an object to JSON.
        /// </summary>
        /// <param name="obj">Object to serialize.</param>
        /// <param name="optionsOverride">A function that takes the current JsonSerializerOptions and returns the modified JsonSerializerOptions.</param>
        /// <typeparam name="T">Type of the object to serialize.</typeparam>
        /// <returns>JSON string.</returns>
        public static string Serialize<T>(T? obj, Func<JsonSerializerOptions, JsonSerializerOptions>? optionsOverride = null)
        {
            if (optionsOverride == null) return JsonSerializer.Serialize(obj, DefaultOptions);
            var options = optionsOverride(GlobalJsonSerializerOptions);
            return JsonSerializer.Serialize(obj, options);
        }

        /// <summary>
        /// Deserialize a JSON string to an object.
        /// </summary>
        /// <param name="json">JSON string to deserialize.</param>
        /// <param name="optionsOverride">A function that takes the current JsonSerializerOptions and returns the modified JsonSerializerOptions.</param>
        /// <typeparam name="T">Type of the object to deserialize to.</typeparam>
        /// <returns>Deserialized object.</returns>
        public static T? Deserialize<T>(string json, Func<JsonSerializerOptions, JsonSerializerOptions>? optionsOverride = null)
        {
            if (optionsOverride == null) return JsonSerializer.Deserialize<T>(json, DefaultOptions);
            var options = optionsOverride(GlobalJsonSerializerOptions);
            return JsonSerializer.Deserialize<T>(json, options);
        }
    }
}